﻿using System;
using System.Collections.Generic;
using System.Linq;
using CoreEx.Common.Extensions;
using CoreEx.Database.Metadata;
using LinFu.IoC.Configuration;

namespace CoreEx.Database.Mapping.Implementation
{
    /// <summary>
    /// Maps a <see cref="Type"/> to a corresponding <see cref="TableInfo"/> instance.
    /// </summary>
    [Implements(typeof(ITableInfoMapper))]
    public class TableInfoMapper : TableInfoMapperBase
    {
        private readonly static IDictionary<TableInfo, Type> _mappedTables = new Dictionary<TableInfo, Type>();

        private static readonly IDictionary<Type, TableInfo> _mappedTypes = new Dictionary<Type, TableInfo>();

        /// <summary>
        /// Initializes a new instance of the <see cref="TableInfoMapper"/> class.
        /// </summary>
        /// <param name="connectionName">The name of the connection used to retrieve a <see cref="IMetadataRepository"/> instance.</param>
        public TableInfoMapper(string connectionName)
            : base(connectionName)
        {
        }

        /// <summary>
        /// Maps the <paramref name="type"/> to a corresponding <see cref="TableInfo"/> instance.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to map.</param>
        /// <returns><see cref="ITableInfoMapper"/></returns>
        public override TableInfo MapFrom(Type type)
        {
            lock (_mappedTables)
            {
                if (_mappedTypes.ContainsKey(type))
                    return _mappedTypes[type];

                var typeName = GetTypeName(type);


                var tableName = typeName.GetBestMatch(MetadataRepository.Tables.Select(t => t.TableName));
                var tableInfo = MetadataRepository.Tables.Where(t => t.TableName == tableName).FirstOrDefault();

                ValidateMapping(type, tableInfo);

                return tableInfo;
            }
        }

        private static void ValidateMapping(Type type, TableInfo tableInfo)
        {
            if (_mappedTables.ContainsKey(tableInfo))
                throw new MappingException(string.Format("Unable to map type '{0}'. The table '{1}' has already been mapped to type '{2}'"
                                                         , type, tableInfo.TableName, _mappedTables[tableInfo]));

            _mappedTables.Add(tableInfo, type);
            _mappedTypes.Add(type, tableInfo);
        }

        /// <summary>
        /// Gets the name of the type that will be used to search for a corresponding table.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static string GetTypeName(Type type)
        {
            //Assume that interfaces has the "I" prefix
            var typeName = type.IsInterface ? type.Name.Substring(1) : type.Name;

            //Assume that the table name is in plural form.
            var pluralName = typeName.ToPlural();

            return pluralName;
        }

    }
}
